# -*- coding:utf-8 -*-

import os
import numpy
from threading import Thread
from x6502 import X6502

DEBUG = 1
DISPLAY_WIDTH = 160
DISPLAY_HEIGHT = 96
DISPLAY_SCALE = 4
DISPLAY_FILL = 0x000000
DISPLAY_EMPTY = 0xFFFFFF


class BA4988(Thread):
    def __init__(self):
        super().__init__()
        self.isRunning = False
        self.display = numpy.zeros((DISPLAY_WIDTH*DISPLAY_SCALE, DISPLAY_HEIGHT*DISPLAY_SCALE))
        self.bank_mapping = [0x0000]*0x10
        self.bank_mapping = [0x0000,
                             0x0001, 0x0002, 0x0003, 0x0004,
                             0x0E94, 0x0E95, 0x0E96, 0x0E97,
                             0x0E80, 0x0E81, 0x0E82, 0x0E83,
                             0x0E88, 0x0E89, 0x0E8A]
        image_path = os.path.join('data', 'BA4988.DAT')
        f = open(image_path, 'rb')
        content = f.read()
        f.close()
        self.memory = bytearray(content[:0x1000])
        self.flash = bytearray(0x1000000)  # 16MB
        # gam_path = r'D:\cbbk\dwn\逆向工具4988.gam'
        # f = open(gam_path, 'rb')
        # content = f.read()
        # f.close()
        # self.memory[0x5000:0x9000] = content[:0x4000]
        # self.memory[0x9000:0xD000] = content[0xC000:0x10000]
        banks = [0x0001,
                 0x0E80, 0x0E84, 0x0E88, 0x0E8C,
                 0x0E90, 0x0E94, 0x0E98, 0x0E9C,
                 0x0EA0, 0x0EA4, 0x0EA8, 0x0EAC,
                 0x0EB0, 0x0EB4, 0x0EB8, 0x0EBC,
                 0x0EC0, 0x0EC4, 0x0EC8, 0x0ECC,
                 0x0ED0, 0x0ED4, 0x0ED8, 0x0EDC,
                 0x0F20, 0x0F24, 0x0F28, 0x0F2C]
        for bank in banks:
            filename = os.path.join('data', '{:04X}.DAT'.format(bank))
            if not os.path.isfile(filename):
                print('not found {:04X}.DAT'.format(bank))
                continue
            f = open(filename, 'rb')
            content = f.read()
            f.close()
            self.flash[bank<<12:(bank<<12)+len(content)] = content

        self.x6502 = X6502(self.getPhysicalAddress, self.RdMem, self.WrMem)
        self.x6502.PC = 0x0350  # system entry point
        # self.x6502.PC = 0x6FA2  # SysStart
        # self.x6502.PC = 0xE907  # void DataBankSwitch(U8 logicStartBank,U8 bankNumber,U16 physicalStartBank);
        # self.x6502.PC = 0xE90A  # void GetDataBankNumber(U8 logicStartBank,U16* physicalBankNumber);
        # self.x6502.PC = 0xD2F6  # __banked_function_call
        # self.x6502.PC = 0xE8F8  # _get_current_bank_number
        # self.x6502.PC = 0xE8FB  # _switch_bank_number
        # self.x6502.PC = 0xE8FE  # _restore_bank_number
        # self.x6502.PC = 0xE90D  # SysWriteCom
        # self.x6502.PC = 0xE910  # SysReadCom

        # self.x6502.PC = 0x5046  # application enter point
        # self.x6502.PC = 0x852A

    def getPhysicalAddress(self, addr):
        return (self.bank_mapping[addr>>12]<<12)+(addr&0xFFF)

    def RdMem(self, addr):
        if addr < 0x1000:
            return self.memory[addr]
        elif addr < 0x10000:
            if self.bank_mapping[addr>>12] == 0:
                RuntimeError('[0x{:04X}]mapping error!'.format(addr>>12))
            flash_addr = (self.bank_mapping[addr>>12]<<12)+(addr&0xFFF)
            return self.flash[flash_addr]
        raise RuntimeError('address: 0x{:04X} is exceed 0xFFFF'.format(addr))

    def WrMem(self, addr, data):
        data &= 0xFF
        if addr < 0x1000:
            self.memory[addr] = data
            if addr == 0x08:
                print('SysWriteCom:', hex(data))
                self.memory[0x09] |= 0x20
            elif addr == 0x0C:  # _BK_SEL
                self.memory[0x0D] = self.bank_mapping[data]&0xFF
                self.memory[0x0E] = self.bank_mapping[data]>>8
            elif addr == 0x0D:  # _BK_ADRL
                self.bank_mapping[self.memory[0x0C]] = (self.bank_mapping[self.memory[0x0C]]&0xFF00) + data
            elif addr == 0x0E:  # _BK_ADRH
                self.bank_mapping[self.memory[0x0C]] = (self.bank_mapping[self.memory[0x0C]]&0x00FF) + (data<<8)
            elif 0x0400 <=addr < 0x0C40:
                y = (0x0C3F-addr)//0x20
                x = addr%0x20
                data = self.memory[addr]
                for i in range(8):
                    if data & (0x80 >> i):
                        self.display[(x*8+i)*DISPLAY_SCALE:(x*8+i+1)*DISPLAY_SCALE,
                            y*DISPLAY_SCALE:(y+1)*DISPLAY_SCALE] = DISPLAY_FILL
                    else:
                        self.display[(x*8+i)*DISPLAY_SCALE:(x*8+i+1)*DISPLAY_SCALE,
                            y*DISPLAY_SCALE:(y+1)*DISPLAY_SCALE] = DISPLAY_EMPTY
            else:
                y = 66+(addr-0x0C40)//0x20
                x = addr%0x20
                for i in range(8):
                    if data & (0x80 >> i):
                        self.display[(x*8+i)*DISPLAY_SCALE:(x*8+i+1)*DISPLAY_SCALE,
                            y*DISPLAY_SCALE:(y+1)*DISPLAY_SCALE] = DISPLAY_FILL
                    else:
                        self.display[(x*8+i)*DISPLAY_SCALE:(x*8+i+1)*DISPLAY_SCALE,
                            y*DISPLAY_SCALE:(y+1)*DISPLAY_SCALE] = DISPLAY_EMPTY
        elif addr < 0x10000:
            if self.bank_mapping[addr>>12] == 0:
                RuntimeError('[0x{:04X}]mapping error!'.format(addr>>12))
            flash_addr = (self.bank_mapping[addr>>12]<<12)+(addr&0xFFF)
            self.flash[flash_addr] = data
        else:
            raise RuntimeError('address: 0x{:04X} is exceed 0xFFFF'.format(addr))

    def run(self) -> None:
        self.isRunning = True
        while self.isRunning:
            if self.x6502.run():
                break
            if self.x6502.PC <= 0x0000 or self.x6502.PC >=0x10000:
                print('PC: {:04X}'.format(self.x6502.PC))
                break
        # print(', '.join(['0x{:04X}'.format(bank) for bank in self.bank_mapping]))

    def stop(self):
        self.isRunning = False

# - 0x0C40
if __name__ == '__main__':
    print('x\ty\tdata\t3B\t3A\t39\t38\t*3A')
    for x in [8]:
        for y in range(96):
            for data in range(1):
                mcu = BA4988()
                mcu.x6502.PC = 0x852A  # SysPutPixel
                mcu.x6502.A = x  # x
                mcu.flash[0x17AF] = y  # y
                mcu.flash[0x17B0] = data  # data
                mcu.run()
                print('{}\t{}\t{}\t\t{:02X}\t{:02X}\t{:02X}\t{:02X}\t{:02X}'.format(
                    x, y, data,
                    mcu.memory[0x3B], mcu.memory[0x3A],
                    mcu.memory[0x39], mcu.memory[0x38],
                    mcu.memory[(mcu.memory[0x3B]<<8)+mcu.memory[0x3A]]
                ))
